
// Main header
#include "../Main.h"

// My header
#include "TextEditor.h"

// Fridge
#include "../Controller/Controller_Main.h"
#include "../Input.h"
#include "../InputKeys.h"
#include "../MathsHelper.h"
#include "../TextRender.h"
#include "../Timer.h"

#include <ctype.h>

static const float fCURSOR_BLINK_INTERVAL = 0.5f;
static const float fKEY_REPEAT_INTERVAL = 0.05f;
static const float fKEY_REPEAT_START_DELAY = 0.8f;
static const u_int uINITIAL_TEXT_CAPACITY = 32;
static const int   iSINGLE_CHAR_WIDTH = 7;

const u_int uMarginX = 4;
const u_int uMarginY = 4;

bool	TextEditor::s_bCursorBlinkState						= true;
float	TextEditor::s_fCursorBlinkTimer						= 0.0f;
float	TextEditor::s_fKeyRepeatTimer						= 0.0f;


TextEditor::TextEditor( Widget* pxParent ) : Panel( pxParent )
{
    m_szText = new char[ uINITIAL_TEXT_CAPACITY ];
	m_szHighlightedText = new char[ uINITIAL_TEXT_CAPACITY ];
	m_szText[0] = 0;
	m_uTextLength = 0;
	m_uTextCapacity = uINITIAL_TEXT_CAPACITY;
    m_uTextColour = 0x000000FF;
    m_uCaretPos = 0;
	m_uHighlightStartPos = 0;
	m_uHighlightEndPos = 0;
	m_pfnLineColourCallback = 0;
	m_bReadOnly = false;
	m_bSingleLine = false;
	m_bNumberOnly = false;
	m_bEnforceCapacityLimit = false;
	m_bMouseDownState = false;
	m_bRecoverFromDoubleClick = false;
	m_bMouseFirstClick = true;
	m_bShowingScrollbarV = false;
	m_bShowingScrollbarH = false;

	// Make new scrollbars even if we don't use them.
	// It's too messy to have a million if-statements checking for whether or not they exist.
	// They start disabled, which sets their width, height, etc. to 0
	m_pxScrollBarV = new ScrollBar( this );
	m_pxScrollBarH = new ScrollBar( this );
	m_pxScrollBarV->SetVisible( false );
	m_pxScrollBarH->SetVisible( false );
	
	memset( &m_xSDLRect, 0, sizeof( SDL_Rect ) );
	
	SetDefaultCursor( INPUT_MOUSE_CURSOR_CARET );
}

TextEditor::~TextEditor()
{
    delete []m_szText;
}

void TextEditor::Render()
{
    Panel::Render();
	
	m_szHighlightedText = new char[ uINITIAL_TEXT_CAPACITY ]; 

	if( m_szText && m_szText[0] )
	{
		// Clip to widget frame
        glEnable( GL_SCISSOR_TEST );
		glScissor( static_cast<int>( GetResizedX() ), 
				   static_cast<int>( Controller_Main::GetHeight() - ( GetResizedY() + GetResizedHeight() ) + m_fBorderWidth + ( m_bShowingScrollbarH ? (int)ScrollBar::uSCROLLBAR_WIDTH : 0 ) ), 
				   static_cast<int>( GetResizedWidth() - ( m_bShowingScrollbarV ? (int)ScrollBar::uSCROLLBAR_WIDTH : 0 ) ), 
				   static_cast<int>( GetResizedHeight() - m_fBorderWidth * 2 - ( m_bShowingScrollbarH ? (int)ScrollBar::uSCROLLBAR_WIDTH : 0 ) ) );
		
		// Prep for text printing
		TextRender::SetColour( m_uTextColour );
		if( m_pfnLineColourCallback )
		{
			TextRender::SetLineColourCallback( m_pfnLineColourCallback );
		}
		TextRender::SetFont( FONT_MONO_10 );
		
		// Deal with scroll bar positions
		const u_int uTextRenderPosX = GetResizedX() + uMarginX - ( m_bShowingScrollbarH ? m_pxScrollBarH->GetScrollbarOffset() : 0 );
		const u_int uTextRenderPosY = GetResizedY() + uMarginY - ( m_bShowingScrollbarV ? m_pxScrollBarV->GetScrollbarOffset() : 0 );
		const u_int uCharHi = TextRender::GetFontAscent();
		
		TextRender::Print( m_szText, uTextRenderPosX, uTextRenderPosY );
		
		// Render the caret
		if( HasFocus() && s_bCursorBlinkState )
		{
			u_int uCaretX, uCaretY;

			TextRender::GetRenderPosOfChar( m_szText, m_uCaretPos, uCaretX, uCaretY );
			++uCaretX;

			const float fPositionX = static_cast< float >( uTextRenderPosX + uCaretX );
			const float fPositionY1 = static_cast< float >( uTextRenderPosY + uCaretY );
			const float fPositionY2 = static_cast< float >( uTextRenderPosY + uCaretY + uCharHi );
			
			glBindTexture( GL_TEXTURE_2D, INVALID_TEXTURE_ID );

			u_int uSwapped = MathsHelper::RGBAtoABGR( m_uTextColour );

			glColor4ubv( reinterpret_cast<GLubyte*>( &uSwapped ) );

			glDisable( GL_LINE_SMOOTH );
			glLineWidth( 1.0f );

			glBegin( GL_LINES );

			glVertex2f( fPositionX, fPositionY1 );
			glVertex2f( fPositionX, fPositionY2 );

			glEnd( );
		}
		
		// Render the highlight
		if ( m_uHighlightStartPos != m_uHighlightEndPos && HasFocus() )
		{
			const u_int uCharHi = TextRender::GetFontAscent();

			u_int uHighlightStartIndex = MACRO_MIN( m_uHighlightStartPos, m_uHighlightEndPos );
			u_int uHighlightEndIndex   = MACRO_MAX( m_uHighlightStartPos, m_uHighlightEndPos );
			u_int uCharStartX = 0;
			u_int uCharStartY = 0;
			u_int uCharEndX   = 0;
			u_int uCharEndY   = 0;
			
			// Set multiply blend mode
			glEnable( GL_BLEND );
			
			glBlendFunc( GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA );
			
			glBindTexture( GL_TEXTURE_2D, INVALID_TEXTURE_ID );
			
			u_int uSwapped = MathsHelper::RGBAtoABGR( 0xBDD3FDFF );
			glColor4ubv( reinterpret_cast<GLubyte*>( &uSwapped ) );
			
			// Draw a quad for each highlighted character
			// TODO: Optomize so it only draws one quad per line
			for (u_int i = uHighlightStartIndex; i < uHighlightEndIndex; i++)
			{
				if ( m_szText [ i ] != '\n' ) 
				{
					u_int uStartPaddingX = 0;
					u_int uEndPaddingX = 0;
					
					if ( i == uHighlightEndIndex - 1 )
					{
						uEndPaddingX = 1;
					}
					
					if ( i == uHighlightStartIndex )
					{
						uStartPaddingX = 1;
					}
					
					TextRender::GetRenderPosOfChar( m_szText, i + 1, uCharEndX, uCharEndY );
					TextRender::GetRenderPosOfChar( m_szText, i, uCharStartX, uCharStartY );
					
					const float fPositionX1 = static_cast< float >( uTextRenderPosX + uCharStartX );
					const float fPositionY1 = static_cast< float >( uTextRenderPosY + uCharStartY );
					
					const float fPositionX2 = static_cast< float >( uTextRenderPosX + uCharEndX + uEndPaddingX );
					const float fPositionY2 = static_cast< float >( uTextRenderPosY + uCharEndY + uCharHi + 2 );
					
					glBegin( GL_QUADS );
					
					glTexCoord2f( 0.0f, 0.0f );
					glVertex2f( fPositionX1, fPositionY1 );
					glTexCoord2f( 0.0f, 1.0f );
					glVertex2f( fPositionX1, fPositionY2 );
					glTexCoord2f( 1.0f, 1.0f );
					glVertex2f( fPositionX2, fPositionY2 );
					glTexCoord2f( 1.0f, 0.0f );
					glVertex2f( fPositionX2, fPositionY1 );
					
					glEnd();
				}
			}
			
			// Restore blending function
			glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
		}
		
		glDisable( GL_SCISSOR_TEST );
	}
}

void TextEditor::Update()
{    
	bool bResetCaretBlink = false;
	bool bResetSelection = false;
	
	// This does several things:
	// - If you have made a selection and you use the scrollbar, when you click back to the editor, your cursor position and selection won't change
	// - If you haven't made a selection and you use the scrollbar, when you click back to the text editor, your cursor will go where you click
	// - If this is the first time you're clicking on the text box, the cursor will go where you click, and no selection will be made.
	if ( ( ( !HasFocus() && m_bMouseDownState && !m_bMouseFirstClick ) || ( !HasFocus() && m_bMouseFirstClick ) ) && ( !m_pxScrollBarV->GetMouseDownState() && !m_pxScrollBarH->GetMouseDownState() ) &&
		m_uHighlightStartPos == m_uHighlightEndPos )
	{
		Input* pxInput = Controller_Main::GetInputHandler();
		m_uCaretPos = TextRender::GetCharAtPos( m_szText,	( pxInput->MousePositionX() + m_pxScrollBarH->GetScrollbarOffset() ) - GetResizedX(),
											   ( pxInput->MousePositionY() + m_pxScrollBarV->GetScrollbarOffset() ) - GetResizedY()  );
		m_uHighlightStartPos = m_uCaretPos;
		m_uHighlightEndPos = m_uCaretPos;
	}
	
	if( HasFocus() )
	{
		Input* pxInput = Controller_Main::GetInputHandler();
		const char* const szBuffer = pxInput->GetTextInputBuffer();

		if( !pxInput )
		{
			return;
		}
			
		InsertText( szBuffer );

		if( pxInput->KeyDown( INPUT_KEY_CURSOR_RIGHT ) )
		{
			if( pxInput->KeyPressed( INPUT_KEY_CURSOR_RIGHT ) || s_fKeyRepeatTimer == 0.0f )
			{
				Caret_Right();

				s_fKeyRepeatTimer = pxInput->KeyPressed( INPUT_KEY_CURSOR_RIGHT ) ? fKEY_REPEAT_START_DELAY : fKEY_REPEAT_INTERVAL;
			}
			
			bResetSelection = true;
			bResetCaretBlink = true;
		}

		if( pxInput->KeyDown( INPUT_KEY_CURSOR_LEFT ) )
		{
			if( pxInput->KeyPressed( INPUT_KEY_CURSOR_LEFT ) || s_fKeyRepeatTimer == 0.0f )
			{
				Caret_Left();

				s_fKeyRepeatTimer = pxInput->KeyPressed( INPUT_KEY_CURSOR_LEFT ) ? fKEY_REPEAT_START_DELAY : fKEY_REPEAT_INTERVAL;
			}
			
			bResetSelection = true;
			bResetCaretBlink = true;
		}
		 
		if( pxInput->KeyDown( INPUT_KEY_CURSOR_DOWN ) )
		{
			if( pxInput->KeyPressed( INPUT_KEY_CURSOR_DOWN ) || s_fKeyRepeatTimer == 0.0f )
			{
				Caret_Down();

				s_fKeyRepeatTimer = pxInput->KeyPressed( INPUT_KEY_CURSOR_DOWN ) ? fKEY_REPEAT_START_DELAY : fKEY_REPEAT_INTERVAL;
			}
			
			bResetSelection = true;
			bResetCaretBlink = true;
		}
		
		if( pxInput->KeyDown( INPUT_KEY_CURSOR_UP ) )
		{
			if( pxInput->KeyPressed( INPUT_KEY_CURSOR_UP ) || s_fKeyRepeatTimer == 0.0f )
			{
				Caret_Up();

				s_fKeyRepeatTimer = pxInput->KeyPressed( INPUT_KEY_CURSOR_UP ) ? fKEY_REPEAT_START_DELAY : fKEY_REPEAT_INTERVAL;
			}
			
			bResetSelection = true;
			bResetCaretBlink = true;
		}
		
		if( pxInput->KeyPressed( INPUT_KEY_HOME ) )
		{
			Home();
			
			bResetSelection = true;
			bResetCaretBlink = true;
		}
		
		if( pxInput->KeyPressed( INPUT_KEY_END ) )
		{
			End();
			
			bResetSelection = true;
			bResetCaretBlink = true;
		}
		
		if( pxInput->KeyDown( INPUT_KEY_DELETE ) )
		{
			bool bResults = RemoveSelectedChars();
			
			if( !bResults && ( pxInput->KeyPressed( INPUT_KEY_DELETE ) || s_fKeyRepeatTimer == 0.0f ) )
			{
				RemoveChar( false );
				s_fKeyRepeatTimer = pxInput->KeyPressed( INPUT_KEY_DELETE ) ? fKEY_REPEAT_START_DELAY : fKEY_REPEAT_INTERVAL;
			}
			else 
			{
				s_fKeyRepeatTimer = fKEY_REPEAT_START_DELAY;
			}
			
			bResetCaretBlink = true;
		}
		
		if( pxInput->KeyDown( INPUT_KEY_BACKSPACE ) )
		{
			bool bResults = RemoveSelectedChars();
			
			if( !bResults && ( pxInput->KeyPressed( INPUT_KEY_BACKSPACE ) || s_fKeyRepeatTimer == 0.0f ) )
			{
				RemoveChar( true );
				s_fKeyRepeatTimer = pxInput->KeyPressed( INPUT_KEY_BACKSPACE ) ? fKEY_REPEAT_START_DELAY : fKEY_REPEAT_INTERVAL;
			}
			else if( bResults ) 
			{
				s_fKeyRepeatTimer = fKEY_REPEAT_START_DELAY;
			}
			
			bResetCaretBlink = true;
		}
		
		if( pxInput->KeyDown( INPUT_KEY_RETURN ) )
		{
			if( !m_bSingleLine )
			{
				RemoveSelectedChars();
				
				if( pxInput->KeyPressed( INPUT_KEY_RETURN ) || s_fKeyRepeatTimer == 0.0f )
				{
					InsertText( "\r\n" );
					s_fKeyRepeatTimer = pxInput->KeyPressed( INPUT_KEY_RETURN ) ? fKEY_REPEAT_START_DELAY : fKEY_REPEAT_INTERVAL;
				}
				
				bResetCaretBlink = true;
			}
		}
		
		if( pxInput->KeyDown( INPUT_KEY_TAB ) )
		{
			if( pxInput->KeyPressed( INPUT_KEY_TAB ) || s_fKeyRepeatTimer == 0.0f )
			{				
				// Tabs are spaces because the TTF renderer doesn't handle them and I can't be arsed
				// writing all the special code to support them -_-
				InsertText( "    " );
				s_fKeyRepeatTimer = pxInput->KeyPressed( INPUT_KEY_TAB ) ? fKEY_REPEAT_START_DELAY : fKEY_REPEAT_INTERVAL;
			}
			
			bResetCaretBlink = true;
		}
		
		// Cut / Copy / Paste
		if( pxInput->KeyDown( INPUT_KEY_CTRL ) )
		{
			const u_int uHighlightStartIndex = MACRO_MIN( m_uHighlightStartPos, m_uHighlightEndPos );
			const u_int uHighlightEndIndex = MACRO_MAX( m_uHighlightStartPos, m_uHighlightEndPos );
			const u_int uHighlightLen = uHighlightEndIndex - uHighlightStartIndex;
			
			s_fKeyRepeatTimer = pxInput->KeyPressed( INPUT_KEY_CTRL ) ? fKEY_REPEAT_START_DELAY : fKEY_REPEAT_INTERVAL;
			
			if( pxInput->KeyDown( INPUT_KEY_CUT ) )
			{
				if( pxInput->KeyPressed( INPUT_KEY_CUT ) || s_fKeyRepeatTimer == 0.0f )
				{
					Clipboard::CopyToClipboard( &m_szText[ uHighlightStartIndex ], uHighlightLen );
					bResetCaretBlink = true;
					RemoveSelectedChars();
					
					s_fKeyRepeatTimer = pxInput->KeyPressed( INPUT_KEY_CUT ) ? fKEY_REPEAT_START_DELAY : fKEY_REPEAT_INTERVAL;
					bResetCaretBlink = true;
					CheckScrolling();
					return;
				}
				
				bResetCaretBlink = true;
			}
			
			if( pxInput->KeyDown( INPUT_KEY_COPY ) )
			{
				if( pxInput->KeyPressed( INPUT_KEY_COPY ) || s_fKeyRepeatTimer == 0.0f )
				{
					Clipboard::CopyToClipboard( &m_szText[ uHighlightStartIndex ], uHighlightLen );
					
					s_fKeyRepeatTimer = pxInput->KeyPressed( INPUT_KEY_COPY ) ? fKEY_REPEAT_START_DELAY : fKEY_REPEAT_INTERVAL;
					bResetCaretBlink = true;
					return;
				}
				
				bResetCaretBlink = true;
			}
			
			if( pxInput->KeyDown( INPUT_KEY_PASTE ) )
			{
				if( pxInput->KeyPressed( INPUT_KEY_PASTE ) || s_fKeyRepeatTimer == 0.0f )
				{
					RemoveSelectedChars();
					InsertText( Clipboard::GetClipboardData() );
					CheckScrolling();
					
					s_fKeyRepeatTimer = pxInput->KeyPressed( INPUT_KEY_PASTE ) ? fKEY_REPEAT_START_DELAY : fKEY_REPEAT_INTERVAL;
				}
				
				bResetCaretBlink = true;
			}
		}

		// Mouse stuff
		if( !m_pxScrollBarV->MouseInWidget() && !m_pxScrollBarH->MouseInWidget() && !m_pxScrollBarV->GetMouseDownState() && !m_pxScrollBarH->GetMouseDownState() )
		{
			int iScrollOffsetX = m_pxScrollBarH->GetScrollbarOffset() + 2;
			int iScrollOffsetY = m_pxScrollBarV->GetScrollbarOffset() + 2;
			
			if( pxInput->MouseButtonPressed( INPUT_MOUSE_BUTTON_LEFT ) && MouseInWidget() && !m_bMouseDownState )
			{				
				if( !m_bMouseFirstClick )
				{
					m_uCaretPos = TextRender::GetCharAtPos( m_szText, ( pxInput->MousePositionX() + iScrollOffsetX ) - GetResizedX(), ( pxInput->MousePositionY() + iScrollOffsetY ) - GetResizedY() );
				}	
				
				m_bMouseFirstClick = false;
				bResetSelection = true;
				bResetCaretBlink = true;
				m_bRecoverFromDoubleClick = false;
			}
			
			if( pxInput->MouseButtonHeld( INPUT_MOUSE_BUTTON_LEFT ) && MouseInWidget() && !m_bRecoverFromDoubleClick )
			{
				m_bMouseDownState = true;
			}

			// Left auto-scroll when making a selection
			if( m_bMouseDownState && iScrollOffsetX < 0 && s_fKeyRepeatTimer == 0.0f && (int)pxInput->MousePositionX() - (int)GetResizedX() - ( m_bShowingScrollbarV ? (int)ScrollBar::uSCROLLBAR_WIDTH : 0 ) <= 0 )
			{
				int iScrollOffset = m_pxScrollBarH->GetScrollbarOffset() - ( iSINGLE_CHAR_WIDTH + 2 ) ;
				m_pxScrollBarH->SetScrollbarOffset( iScrollOffset );
				
				bResetCaretBlink = true;
				s_fKeyRepeatTimer = fKEY_REPEAT_INTERVAL;
			}
						
			// Up auto-scroll
			if( m_bMouseDownState && iScrollOffsetY > 0 && s_fKeyRepeatTimer == 0.0f && (int)pxInput->MousePositionY() - (int)GetResizedY() - ( m_bShowingScrollbarH ? (int)ScrollBar::uSCROLLBAR_WIDTH : 0 ) <= 0 )
			{
				int iScrollOffset = m_pxScrollBarV->GetScrollbarOffset() - ( TextRender::GetFontAscent() + 2 ) ;
				m_pxScrollBarV->SetScrollbarOffset( iScrollOffset );
				
				bResetCaretBlink = true;
				s_fKeyRepeatTimer = fKEY_REPEAT_INTERVAL;
			}
			
			// Right / Down auto-scroll are taken care of by removing the MouseMoved check.
			if ( m_bMouseDownState && !m_bMouseFirstClick )
			{				
				iScrollOffsetY += 2;
				m_uCaretPos = TextRender::GetCharAtPos( m_szText, ( pxInput->MousePositionX() + iScrollOffsetX ) - GetResizedX(), ( pxInput->MousePositionY() + iScrollOffsetY ) - GetResizedY() );
				m_uHighlightEndPos = m_uCaretPos;
				
				bResetCaretBlink = true;
			}

			if( pxInput->MouseButtonReleased( INPUT_MOUSE_BUTTON_LEFT ) )
			{
				m_bMouseDownState = false;
			}
			
			if( pxInput->MouseButtonDoubleClick( INPUT_MOUSE_BUTTON_LEFT ) )
			{
				m_uHighlightStartPos = m_uCaretPos;
				m_uHighlightEndPos = m_uCaretPos;
								
				u_int uTemp = m_uCaretPos - 1;
				
				// Numbers
				if( m_szText[ uTemp ] )
				{
					if( isdigit( m_szText[ uTemp ] ) || ( m_szText[ uTemp ] == '.' ) )
					{
						// Look backwards
						while( isdigit( m_szText[ uTemp ] ) || ( m_szText[ uTemp ] == '.' ) )
						{
							m_uHighlightStartPos = uTemp;
							uTemp--;
						}
						
						// Look forwards
						uTemp = m_uCaretPos;
						while( isdigit( m_szText[ uTemp ] ) || ( m_szText[ uTemp ] == '.' ) )
						{
							m_uHighlightEndPos = uTemp;
							uTemp++;
						}
						
						if( uTemp > m_uCaretPos )
						{
							m_uHighlightEndPos++;
						}
						
						m_uCaretPos = m_uHighlightEndPos;
						
						m_bMouseDownState = false;
						m_bRecoverFromDoubleClick = true;
					}
					// Letters
					else if( isalpha( m_szText[ uTemp ] ) )
					{
						// Look backwards
						while( isalpha( m_szText[ uTemp ] ) )
						{
							m_uHighlightStartPos = uTemp;
							uTemp--;
						}
						
						// Look forwards
						uTemp = m_uCaretPos;
						while( isalpha( m_szText[ uTemp ] ) || ( m_szText[ uTemp ] == '.' ) )
						{
							m_uHighlightEndPos = uTemp;
							uTemp++;
						}
						
						if( uTemp > m_uCaretPos )
						{
							m_uHighlightEndPos++;
						}
						
						m_uCaretPos = m_uHighlightEndPos;
						
						m_bMouseDownState = false;
						m_bRecoverFromDoubleClick = true;
					}
				}
				
				bResetSelection = false;
			}
			 
			CheckScrolling();
		}
		else if( pxInput->MouseButtonHeld( INPUT_MOUSE_BUTTON_LEFT ) && !m_bMouseDownState )
		{
			// Reset first click when using the scrollbar. 
			// This ensures that a highlight selection won't be made when you first click the editor after dragging the scrollbar.
			m_bMouseFirstClick = true;
		}
	}
	
	if( bResetSelection )
	{
		m_uHighlightStartPos = m_uCaretPos;
		m_uHighlightEndPos = m_uCaretPos;
	}

	if( bResetCaretBlink )
	{
		s_fCursorBlinkTimer = 0.0f;
		s_bCursorBlinkState = true;
	}
	else
	{
		s_fCursorBlinkTimer += Timer::GetFrameTime();
		if( s_fCursorBlinkTimer > fCURSOR_BLINK_INTERVAL )
		{
			s_bCursorBlinkState = !s_bCursorBlinkState;
			s_fCursorBlinkTimer = 0.0f;
		}
	}

	s_fKeyRepeatTimer -= Timer::GetFrameTime();
	if( s_fKeyRepeatTimer < 0.0f )
	{
		s_fKeyRepeatTimer = 0.0f;
	}
	
	Panel::Update();
}

void TextEditor::CheckScrolling()
{
	u_int uTextRenderWidth = 0;
	u_int uTextRenderHeight = 0;
	u_int uCaretX = 0; 
	u_int uCaretY = 0;
	
	TextRender::GetRenderSize( m_szText, uTextRenderWidth, uTextRenderHeight );
	TextRender::GetRenderPosOfChar( m_szText, m_uCaretPos, uCaretX, uCaretY );
	
	// V Scroll
	if( m_bShowingScrollbarV )
	{
		// Compensate for border
		uTextRenderHeight += uMarginY - 1;
		
		// Update scrollbar
		m_pxScrollBarV->SetParentContentSize( MACRO_MAX( uTextRenderHeight, ( GetResizedHeight() + m_pxScrollBarV->GetScrollbarOffset() - 2 ) - ( m_bShowingScrollbarH ? ScrollBar::uSCROLLBAR_WIDTH : 0 ) ) );
		m_pxScrollBarV->SetParentVisibleSize( GetResizedHeight() - 2 - ( m_bShowingScrollbarH ? ScrollBar::uSCROLLBAR_WIDTH : 0 ) );
		
		const u_int uCharHi = TextRender::GetFontAscent();
		const u_int uMaxLinesInEditor = ( ( GetResizedHeight() - ( m_bShowingScrollbarH ? ScrollBar::uSCROLLBAR_WIDTH : 0 ) ) / uCharHi ) - 1;
		
		// Down Y
		while( uCaretY >= uMaxLinesInEditor * uCharHi + m_pxScrollBarV->GetScrollbarOffset() )
		{
			int iScrollOffset = m_pxScrollBarV->GetScrollbarOffset() + 1;
			m_pxScrollBarV->SetScrollbarOffset( iScrollOffset );
		}	
		
		// Up Y
		while( m_pxScrollBarV->GetScrollbarOffset() > 0 && (int)uCaretY + uCharHi + 2 - m_pxScrollBarV->GetScrollbarOffset() <= uCharHi + 2 )
		{
			int iScrollOffset = m_pxScrollBarV->GetScrollbarOffset() - 1;
			m_pxScrollBarV->SetScrollbarOffset( iScrollOffset );
		}
	}
	
	// H Scroll
	if( m_bShowingScrollbarH )
	{
		// Compensate for border
		uTextRenderWidth += uMarginX - 1;
		
		// Update scrollbar
		m_pxScrollBarH->SetParentContentSize( MACRO_MAX( uTextRenderWidth, ( GetResizedWidth() + m_pxScrollBarH->GetScrollbarOffset() - 2 ) - ( m_bShowingScrollbarV ? ScrollBar::uSCROLLBAR_WIDTH : 0 ) ) );
		m_pxScrollBarH->SetParentVisibleSize( GetResizedWidth() - 2 - ( m_bShowingScrollbarV ? ScrollBar::uSCROLLBAR_WIDTH : 0 ) );
		
		const u_int uMaxColumnsInEditor = ( ( GetResizedWidth() - ( m_bShowingScrollbarV ? ScrollBar::uSCROLLBAR_WIDTH : 0 ) ) / iSINGLE_CHAR_WIDTH );
		
		// Left X
		while( uCaretX >= uMaxColumnsInEditor * iSINGLE_CHAR_WIDTH + m_pxScrollBarH->GetScrollbarOffset() )
		{
			int iScrollOffset = m_pxScrollBarH->GetScrollbarOffset() + 1;
			m_pxScrollBarH->SetScrollbarOffset( iScrollOffset );
		}	
		
		// Right X
		while( m_pxScrollBarH->GetScrollbarOffset() > 0 && ( (int)uCaretX + iSINGLE_CHAR_WIDTH + 2 ) - m_pxScrollBarH->GetScrollbarOffset() <= iSINGLE_CHAR_WIDTH - 2 )
		{
			int iScrollOffset = m_pxScrollBarH->GetScrollbarOffset() - 1;
			m_pxScrollBarH->SetScrollbarOffset( iScrollOffset );
		}
	}
}

void TextEditor::OnGetFocus()
{
	s_fCursorBlinkTimer = 0.0f;
	s_bCursorBlinkState = true;
	
	//m_uHighlightStartPos = m_uCaretPos;
	//m_uHighlightEndPos = m_uCaretPos;
}

void TextEditor::OnLoseFocus()
{
	//m_uHighlightStartPos = m_uCaretPos;
	//m_uHighlightEndPos = m_uCaretPos;
	
	// Return scrolling to start position if we're a single line field
	if( m_bSingleLine )
	{
		//m_iScrollOffsetX = 
		//m_iScrollOffsetY = 0;
	}
}

void TextEditor::SetText( const char* szText )
{
	if( szText && szText[0] )
	{
		// Reset scrolling
		if( m_bShowingScrollbarH )
		{
			m_pxScrollBarH->SetScrollbarOffset( 0 );
		}
		
		if( m_bShowingScrollbarV )
		{
			m_pxScrollBarV->SetScrollbarOffset( 0 );
		}
		
		m_uTextLength = strlen( szText );

		if( m_uTextCapacity < m_uTextLength + 2 )
		{
			delete []m_szText;
        
			m_uTextCapacity = m_uTextLength * 2;
			m_szText = new char[ m_uTextCapacity ];
		}
			//strncpy( m_szText, m_uTextLength + 1, szText, m_uTextLength + 1 );
			strncpy( m_szText, szText, m_uTextLength + 1 );
		
		CheckScrolling();
	}
    else
    {
        delete []m_szText;
        m_szText = NULL;
        m_uTextLength = 0;
		m_uTextCapacity = 0;
    }

    m_uCaretPos = m_uTextLength;
	if( m_uCaretPos > 0 && m_szText[m_uCaretPos] == '\n' )
	{
		--m_uCaretPos;
	}
	
	m_uCaretPos = 0;
	m_uHighlightStartPos = m_uCaretPos;
	m_uHighlightEndPos = m_uCaretPos;
}

void TextEditor::AppendText( const char* szText )
{
	m_uCaretPos = m_uTextLength;
	InsertText( szText );
}

void TextEditor::Caret_Up()
{
	// Find out how many chars we are into this line
				
	if ( m_uCaretPos > 0 )
	{
		u_int uTemp = m_uCaretPos;

		while( uTemp && m_szText[ uTemp ] != '\n' )
		{
			--uTemp;
			
			if (uTemp == 0)
			{
				return;
			}
		}

		u_int uLineChars = ( m_uCaretPos - uTemp );

		// Find the previous line

		if( uTemp )
		{
			// Skip \r
			--uTemp;
		}

		while( uTemp && m_szText[ uTemp ] && m_szText[ uTemp ] != '\n' )
		{
			--uTemp;
		}

		m_uCaretPos = uTemp;
		
		if( m_uCaretPos == 0 )
		{
			--uLineChars;
		}

		while( uLineChars && m_szText[ m_uCaretPos ] && m_szText[ m_uCaretPos ] != '\r' )
		{
			--uLineChars;
			++m_uCaretPos;
		}
	}
}

void TextEditor::Caret_Down()
{
	// Find out how many chars we are into this line

	u_int uTemp = m_uCaretPos;
	if( m_uCaretPos == 0 )
	{
		++uTemp;
	}
			
	while( uTemp && m_szText[ uTemp ] != '\n' )
	{
		--uTemp;
	}

	u_int uLineChars = ( m_uCaretPos - uTemp );
	
	// Find the the next line
	
	uTemp = m_uCaretPos;
	
	if( (int)(m_uCaretPos - uLineChars - 1) < 1 )
	{	
		++uLineChars;
	}
	while( m_szText[ uTemp ] && m_szText[ uTemp ] != '\n' )
	{
		++uTemp;
	}
	if( m_szText[ uTemp ] )
	{
		m_uCaretPos = uTemp;
		while( uLineChars && m_szText[ m_uCaretPos ] && m_szText[ m_uCaretPos ] != '\r' )
		{
			--uLineChars;
			++m_uCaretPos;
		}
	}
}

void TextEditor::Caret_Left()
{
	if( m_uCaretPos > 0 )
	{
		--m_uCaretPos;
		if( m_szText[ m_uCaretPos ] == '\n' )
		{
			--m_uCaretPos;
		}
	}
}

void TextEditor::Caret_Right()
{
	if( m_szText[ m_uCaretPos ] != 0 )
	{
		++m_uCaretPos;
		if( m_szText[ m_uCaretPos ] == '\n' )
		{
			++m_uCaretPos;
		}
	}
}

void TextEditor::Home()
{
	while( m_uCaretPos != -1 && m_szText[ m_uCaretPos ] != '\n' )
	{
		--m_uCaretPos;
	}
	++m_uCaretPos;
}

void TextEditor::End()
{
	while( m_szText[ m_uCaretPos ] && m_szText[ m_uCaretPos ] && m_szText[ m_uCaretPos ] != '\n' )
	{
		++m_uCaretPos;
	}
	if( m_szText[ m_uCaretPos ] )
	{
		--m_uCaretPos;
	}
}

void TextEditor::InsertText( const char* szText )
{
	const u_int uTextLen = strlen( szText );
	if( !szText || uTextLen == 0 )
	{
		return;
	}
	
	RemoveSelectedChars();
	
	// Deal with numbers only
	if( m_bNumberOnly )
	{
		for (u_int i = 0; i < strlen( szText ); i++)
		{
			if( !( isdigit( szText[i] ) || strncmp( &szText[i], ".", 1) == 0 || strncmp( &szText[i], " ", 1) == 0 ) )
			{
				return;
			}
		}
	}

	SetModified( true );

	if( m_uTextLength >= m_uTextCapacity - 1 && m_bEnforceCapacityLimit )
	{
		return;
	}
	
	if( m_uTextLength + uTextLen + 1 > m_uTextCapacity )
	{
		m_uTextCapacity = MACRO_MAX( m_uTextCapacity * 2, m_uTextLength + uTextLen + 1 );
		char* szNewText = new char[ m_uTextCapacity ];
		memcpy( szNewText, m_szText, m_uTextLength );
		szNewText[ m_uTextLength ] = 0;
		delete []m_szText;
		m_szText = szNewText;
	}

	if( m_uCaretPos != m_uTextLength )
	{
		memmove( &m_szText[ m_uCaretPos + uTextLen ], &m_szText[ m_uCaretPos ], ( m_uTextLength + 1 ) - m_uCaretPos );
	}
	memcpy( &m_szText[ m_uCaretPos ], szText, uTextLen );
	
	m_uCaretPos += uTextLen;

	m_uTextLength += uTextLen;

	m_szText[ m_uTextLength ] = 0;
}

void TextEditor::RemoveChar( bool bBeforeCaret, bool bInSelection )
{
	if( bBeforeCaret && !m_uCaretPos )
	{
		return;
	}
	if( !bBeforeCaret && m_uCaretPos == m_uTextLength )
	{
		return;
	}

	if( !bBeforeCaret )
	{
		++m_uCaretPos;
		if( m_szText[ m_uCaretPos ] == '\n' )
		{
			++m_uCaretPos;
		}
	}

	if( !m_uCaretPos )
	{
		return;
	}

	const u_int uOldCaretPos = m_uCaretPos;

	--m_uCaretPos;
		
	if( m_szText[ m_uCaretPos ] == '\n' )
	{
		--m_uCaretPos;
	}

	memmove( &m_szText[ m_uCaretPos ], &m_szText[ m_uCaretPos + ( uOldCaretPos - m_uCaretPos ) ], m_uTextLength - m_uCaretPos );

	m_uTextLength = strlen( m_szText );

	SetModified( true );
}

bool TextEditor::RemoveSelectedChars()
{
	if( m_uHighlightStartPos != m_uHighlightEndPos )
	{				
		m_uCaretPos = MACRO_MAX( m_uHighlightStartPos, m_uHighlightEndPos );
		
		const u_int uTextLen = MACRO_MAX( m_uHighlightStartPos, m_uHighlightEndPos ) - MACRO_MIN( m_uHighlightStartPos, m_uHighlightEndPos );
		const u_int uOldCaretPos = m_uCaretPos;
		
		for( u_int i = 0; i < uTextLen; i++ )
		{
			--m_uCaretPos;
		}
		
		memmove( &m_szText[ m_uCaretPos ], &m_szText[ m_uCaretPos + ( uOldCaretPos - m_uCaretPos ) ], m_uTextLength - m_uCaretPos );
		
		m_uHighlightStartPos = m_uCaretPos;
		m_uHighlightEndPos = m_uCaretPos;
		
		m_uTextLength = strlen( m_szText );
		
		SetModified( true );
		
		return true;
	}
	
	return false;
}

void TextEditor::SetShowScrollBars( bool bShowScrollBarV, bool bShowScrollBarH )
{
	if( m_bShowingScrollbarV == bShowScrollBarV && m_bShowingScrollbarH == bShowScrollBarH )
	{
		return;
	}
	
	if( bShowScrollBarV	)
	{
		m_pxScrollBarV->SetEnabled( true );
		m_pxScrollBarV->SetVisible( true );
		m_bShowingScrollbarV = true;
	}
	
	if( bShowScrollBarH )
	{
		m_pxScrollBarH->SetEnabled( true );
		m_pxScrollBarH->SetVisible( true );
		m_pxScrollBarH->SetHorizontal( true );
		m_bShowingScrollbarH = true;
	}
	
	if( m_bShowingScrollbarV && m_bShowingScrollbarH )
	{
		m_pxScrollBarV->SetUsingBothBars( true );
		m_pxScrollBarH->SetUsingBothBars( true );
	}
}

void TextEditor::SetReadOnly( bool bReadOnly )
{
	m_bReadOnly = bReadOnly;
}

void TextEditor::SetNumberOnly( bool bNumberOnly )
{
	m_bNumberOnly = bNumberOnly;
}

void TextEditor::Clear()
{
	SetText("");
}

void TextEditor::SetSingleLine( bool bSingleLine )
{
	m_bSingleLine = bSingleLine;
}

void TextEditor::SetLengthLimit( u_int uLengthLimit )
{
	m_uTextCapacity = uLengthLimit + 1;
	m_bEnforceCapacityLimit = true;
}

// eof
